-
Notifications
You must be signed in to change notification settings - Fork 0
[25.10.04 / TASK-250] Feature - 주간 뉴스레터 수신거부 API 추가 #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Walkthrough뉴스레터 구독 해지 흐름을 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant R as Router
participant C as UserController
participant S as UserService
participant Repo as UserRepository
participant DB as Database
U->>R: GET /newsletter-unsubscribe?email=...
R->>C: unsubscribeNewsletter(req)
alt 이메일 누락
C-->>U: 302 Redirect /main (로깅, next 미호출)
else 이메일 존재
C->>S: unsubscribeNewsletter(email)
S->>Repo: findByUserEmail(email)
Repo->>DB: SELECT user WHERE email=?
DB-->>Repo: User | null
alt 사용자 없음
Repo-->>S: null (로그 후 반환)
S-->>C: void
C-->>U: 302 Redirect /main
else 사용자 있음
S->>Repo: unsubscribeNewsletter(user.id)
Repo->>DB: UPDATE newsletter_subscribed=false
DB-->>Repo: OK
Repo-->>S: void
S-->>C: void
C-->>U: 302 Redirect /main
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/repositories/user.repository.ts (1)
166-174
: 업데이트 검증 로직 추가 고려현재 구현은 쿼리 실행 후 업데이트된 행의 수를 확인하지 않습니다. 존재하지 않는 사용자 ID가 전달되어도 오류 없이 성공으로 처리됩니다.
이것이 의도된 동작인지 확인해주세요. 만약 존재하지 않는 사용자에 대한 명시적인 에러 처리가 필요하다면 다음과 같이 개선할 수 있습니다:
async unsubscribeNewsletter(id: number): Promise<void> { try { - const query = `UPDATE "users_user" SET newsletter_subscribed = false WHERE id = $1`; - await this.pool.query(query, [id]); + const query = `UPDATE "users_user" SET newsletter_subscribed = false WHERE id = $1 RETURNING id`; + const result = await this.pool.query(query, [id]); + if (result.rowCount === 0) { + throw new DBError('해당 사용자를 찾을 수 없습니다.'); + } } catch (error) { logger.error('User Repo unsubscribeNewsletter Error : ', error); throw new DBError('뉴스레터 구독 해제 중 문제가 발생했습니다.'); } }src/routes/user.router.ts (1)
172-172
: GET 메서드 사용의 보안 고려사항이메일에서 직접 클릭하는 구독 해제 링크의 특성상 GET 메서드 사용이 일반적이고 사용자 편의성이 높습니다. 다만 다음의 보안 고려사항이 있습니다:
- 이메일 스캐너나 브라우저 프리페치가 의도치 않게 구독 해제를 트리거할 수 있음
- CSRF 보호가 없음
- 로그나 브라우저 히스토리에 이메일이 노출됨
보안을 강화하려면 다음 접근을 고려할 수 있습니다:
- 이메일 대신 일회성 토큰을 쿼리 파라미터로 사용
- 토큰 기반으로 사용자를 식별하고 만료 시간 설정
- 구독 해제 확인 페이지를 표시하고 POST 요청으로 최종 처리
현재 구현도 일반적인 패턴이므로 비즈니스 요구사항에 따라 판단하시면 됩니다.
src/controllers/__test__/user.controller.test.ts (1)
409-447
: 테스트 커버리지 개선 제안기본적인 성공/실패 케이스는 잘 커버되어 있습니다. 다음의 추가 테스트 케이스를 고려해보세요:
it('서비스에서 에러 발생 시 nextFunction에 에러를 전달해야 한다', async () => { const email = 'test@example.com'; mockRequest.query = { email }; const error = new Error('Database error'); mockUserService.unsubscribeNewsletter.mockRejectedValue(error); await userController.unsubscribeNewsletter( mockRequest as Request, mockResponse as Response, nextFunction ); expect(nextFunction).toHaveBeenCalledWith(error); expect(mockResponse.redirect).not.toHaveBeenCalled(); }); it('이메일이 빈 문자열인 경우 BadRequestError를 던져야 한다', async () => { mockRequest.query = { email: '' }; await userController.unsubscribeNewsletter( mockRequest as Request, mockResponse as Response, nextFunction ); expect(nextFunction).toHaveBeenCalledWith( expect.objectContaining({ message: '이메일이 필요합니다.', }) ); });src/controllers/user.controller.ts (1)
173-186
: 이메일 검증 및 PII 로깅 개선 고려현재 구현은 이메일 존재 여부만 확인하고 형식은 검증하지 않습니다. 또한 오류 로그에 이메일이 노출됩니다.
다음 개선 사항들을 고려해보세요:
1. 이메일 형식 검증 추가 (선택사항):
if (!email || typeof email !== 'string' || !email.includes('@')) { throw new BadRequestError('올바른 이메일 형식이 아닙니다.'); }2. PII 로깅 최소화:
} catch (error) { - logger.error(`뉴스레터 구독 해제 실패: [email: ${req.query.email}]`, error); + logger.error(`뉴스레터 구독 해제 실패`, error); next(error); }현재 구현도 기본적인 요구사항을 충족하지만, 위 개선 사항들은 보안과 개인정보 보호를 강화할 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/controllers/__test__/user.controller.test.ts
(1 hunks)src/controllers/user.controller.ts
(2 hunks)src/repositories/user.repository.ts
(4 hunks)src/routes/user.router.ts
(1 hunks)src/services/user.service.ts
(1 hunks)src/types/models/User.type.ts
(1 hunks)src/utils/fixtures.ts
(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-12-05T17:21:07.909Z
Learnt from: HA0N1
PR: Check-Data-Out/velog-dashboard-v2-api#6
File: src/reposities/user.repository.ts:7-15
Timestamp: 2024-12-05T17:21:07.909Z
Learning: Function `findByUserEmail` in `src/reposities/user.repository.ts` is scheduled to be deleted and should be ignored in future code reviews.
Applied to files:
src/repositories/user.repository.ts
🧬 Code graph analysis (2)
src/controllers/user.controller.ts (2)
src/types/dto/responses/emptyReponse.type.ts (1)
EmptyResponseDto
(17-17)src/exception/badRequest.exception.ts (1)
BadRequestError
(3-7)
src/repositories/user.repository.ts (3)
src/types/models/User.type.ts (1)
User
(1-16)src/types/index.ts (1)
User
(2-2)src/exception/db.exception.ts (1)
DBError
(3-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: build-and-test (23)
- GitHub Check: build-and-test (21)
- GitHub Check: build-and-test (22)
🔇 Additional comments (7)
src/types/models/User.type.ts (1)
14-15
: LGTM!
newsletter_subscribed
필드 추가가 적절합니다. 타입 정의가 명확하고 다른 필드들과 일관성 있게 구성되었습니다.src/repositories/user.repository.ts (3)
10-10
: LGTM!반환 타입에
| null
을 추가하여 타입 안전성이 개선되었습니다. 실제 구현 로직과 타입 시그니처가 일치합니다.Also applies to: 20-20
30-38
: 과거 리뷰 컨텍스트와 충돌 확인 필요검색된 학습 내용에 따르면
findByUserEmail
함수는 삭제 예정이었습니다. 이 PR에서 해당 메서드를 새로 추가하고 있는데, 요구사항이 변경되었는지 확인이 필요합니다.Based on learnings.
구현 자체는 올바릅니다:
- 파라미터화된 쿼리로 SQL 인젝션 방지
- 일관된 에러 핸들링
168-168
: 마이그레이션 및 스키마 검증 필요
newsletter_subscribed
컬럼을 추가하는 마이그레이션 파일이 보이지 않습니다. 실제 DB 스키마에 해당 컬럼과 기본값 설정이 존재하는지 확인해주세요.src/utils/fixtures.ts (1)
40-40
: LGTM!테스트 픽스처에
newsletter_subscribed
필드가 적절하게 추가되었습니다. 구독 상태(true
)로 설정되어 구독 해제 기능 테스트에 적합합니다.src/routes/user.router.ts (1)
154-171
: LGTM!Swagger 문서가 완전하고 명확합니다. 필수 쿼리 파라미터와 리디렉션 응답이 정확하게 정의되어 있습니다.
src/controllers/user.controller.ts (1)
7-7
: LGTM!
BadRequestError
임포트가 적절하게 추가되었습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/services/user.service.ts (1)
178-181
: 이전 리뷰에서 이미 지적된 문제입니다.사용자를 찾지 못한 경우 조용히 실패하는 이 동작은 이전 리뷰에서 이미 논의되었습니다. 보안상 이메일 열거 공격을 방지하려는 의도라면, 해당 설계 결정을 명확히 하는 주석을 추가하는 것이 좋습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/services/user.service.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/services/user.service.ts (1)
src/modules/slack/slack.notifier.ts (1)
sendSlackMessage
(23-28)
🔇 Additional comments (1)
src/services/user.service.ts (1)
190-194
: Slack 알림 에러 처리 잘 되어 있습니다.Slack 알림 실패가 메인 비즈니스 로직(구독 해제)에 영향을 주지 않도록 격리된 에러 처리를 적용한 것은 좋은 패턴입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타입 강화를 더 돈독히 해주셨군요. (애초에 이게 TS 린팅에 안걸리는게 더 신기하네..)
const email = req.query.email as string; | ||
if (!email) { | ||
throw new BadRequestError('이메일이 필요합니다.'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 에러를 받으면 그러면 사용자는 어떻게 해야 하나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
하나 치명적인 엣지 케이스에 대해 여쭙고 싶습니다.
구독 취소를 한 번 이라도 눌러본 사람이라면, API 호출을 뭘 하는지 누구나 볼 수 있게 될 겁니다.
어떤 사람이 악의를 가지고, 구독 취소 API 뒤에 쿼리 스트링값으로, 부르트포스 공격을 하면 어떻게 되나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 잘 읽었습니다!
현우님이 해 주신 질문에 동감하고 있고, 그 외에는 큰 문제는 없는 것 같네요
좋았던 점
- 타입 지정을 깔끔하게 강화해 주신 점이 좋았습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
리뷰가 너무 늦었네요🥲 고생하셨습니다!!
좋았던 점
- 타입이 일치하도록 개선해주신 부분이 좋았습니다!
- 이메일 검증 로직을 추가해 각 단계별로 에러 로깅을 남겨서 추적 가능하게 한 점이 인상적이었습니다.
🔥 변경 사항
주간 뉴스레터 수신 거부 API(
/user/newsletter-unsubscribe
)를 추가하였습니다./main
으로 리디렉트🏷 관련 이슈
📸 스크린샷 (UI 변경 시 필수)
X
📌 체크리스트
Summary by CodeRabbit